// Copyright 2001-2009 freehep
package org.xmind.org.freehep.graphicsio.font;

import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphMetrics;
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;

import org.xmind.org.freehep.graphics2d.font.CharTable;
import org.xmind.org.freehep.util.io.ASCIIHexOutputStream;
import org.xmind.org.freehep.util.io.CountedByteOutputStream;
import org.xmind.org.freehep.util.io.EEXECEncryption;

/**
 * Font embedder for type 1 fonts. The output can be directly fed into a ps file
 * or as a FontFile to a pdf file. <h3>Todo</h3>
 * <ul>
 * <li>use subroutines for accents
 * <li>add more hints
 * </ul>
 *
 * @author Simon Fischer
 * @author Jason Wong
 */
@SuppressWarnings("nls")
public class FontEmbedderType1 extends FontEmbedder {

    /** Defines whether or not eexec encryption is used. */
    private static final boolean ENCRYPT = true;

    /**
     * Defines whether or not encrypted part should be hex encoded (otherwise it
     * is binary).
     */
    private static final boolean HEX_ENC = true;

    /**
     * Defines whether or not encrypted charstrings should be hex encoded
     * Ghostview crashes when set to true. The freehep ps interpreter handles it
     * correctly.
     */
    private static boolean HEX_ENC_CHARSTRINGS = false;

    private PrintStream fontFile, encrypted;

    private CountedByteOutputStream byteCounter;

    private int asciiEnd, encEnd; // remember the lengths of the three
                                  // portions

    private boolean addZeros;

    public FontEmbedderType1(FontRenderContext context, OutputStream out,
            boolean addZeros) {
        super(context);
        this.byteCounter = new CountedByteOutputStream(out);
        this.fontFile = new PrintStream(byteCounter);
        this.addZeros = addZeros;
        asciiEnd = encEnd = -1;
    }

    protected void writeWidths(double[] w) throws IOException {
    }

    protected void writeEncoding(CharTable t) throws IOException {
        fontFile.println("/Encoding 256 array");
        fontFile.println("0 1 255 {1 index exch /.notdef put} for"); // set
                                                                     // undefined
                                                                     // to
                                                                     // .notdef
                                                                     // ??
        for (int i = 0; i < 256; i++) {
            String charName = t.toName(i);
            if (charName != null)
                fontFile.println("dup " + i + " /" + charName + " put");
        }
        fontFile.println("readonly def");
    }

    protected void openIncludeFont() throws IOException {

        // begin clear text ascii portion
        fontFile.println("%!FontType1-1.0: " + getFont().getName()); // unknown
                                                                     // version
                                                                     // number
        // fontFile.println("%%CreationDate: " +
        // DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).
        // format(new Date()));
        fontFile.println("% Generated by: " + getClass().getName());
        fontFile.println("11 dict begin");

        fontFile.println("/FontInfo 8 dict dup begin");
        fontFile.println("/FullName (" + getFont().getPSName()
                + ") readonly def");
        fontFile.println("/FamilyName (" + getFont().getFamily()
                + ") readonly def");
        fontFile.println("end readonly def");

        fontFile.println("/FontName /" + getFontName() + " def");
        fontFile.println("/PaintType 0 def");
        fontFile.println("/FontType 1 def");
        fontFile.println("/FontMatrix [" + 1 / FONT_SIZE + " 0.0 0.0 " + 1
                / FONT_SIZE + " 0.0 0.0] readonly def");
    }

    protected void closeIncludeFont() {

        Rectangle2D boundingBox = getFontBBox();
        int llx = (int) Math.round(boundingBox.getX());
        int lly = (int) Math.round(boundingBox.getY());
        int urx = (int) Math.round(boundingBox.getX() + boundingBox.getWidth());
        int ury = (int) Math
                .round(boundingBox.getY() + boundingBox.getHeight());
        fontFile.println("/FontBBox {" + llx + " " + lly + " " + urx + " "
                + ury + "} readonly def");

        fontFile.println("currentdict end");

        // begin encrypted portion
        if (ENCRYPT) {
            fontFile.print("currentfile eexec ");
            asciiEnd = byteCounter.getCount();
        }
        fontFile.flush();
    }

    protected void openGlyphs() throws IOException {
        // begin encryption
        if (ENCRYPT) {
            if (HEX_ENC) {
                encrypted = new PrintStream(new EEXECEncryption(
                        new ASCIIHexOutputStream(fontFile),
                        EEXECEncryption.EEXEC_R));
            } else {
                encrypted = new PrintStream(new EEXECEncryption(fontFile,
                        EEXECEncryption.EEXEC_R));
            }
        } else {
            encrypted = fontFile;
        }

        // begin the Private dictionary
        encrypted.println("dup /Private 8 dict dup begin");
        encrypted
                .println("/RD {string currentfile exch readstring pop} executeonly def");
        encrypted.println("/ND {noaccess def} executeonly def");
        encrypted.println("/NP {noaccess put} executeonly def");
        encrypted.println("/BlueValues [] def"); // ???
        encrypted.println("/MinFeature {16 16} def");
        encrypted.println("/password 5839 def");
        encrypted.print("2 index ");
        encrypted.println("/CharStrings " + (getNODefinedChars() + 1)
                + " dict dup begin");
    }

    protected void closeGlyphs() throws IOException {
        encrypted.println("end"); // end Private
        encrypted.println("end"); // end CharStrings
    }

    protected void closeEmbedFont() throws IOException {
        encrypted.println("readonly put");
        encrypted.println("noaccess put");
        encrypted.println("dup /FontName get exch definefont pop");
        encrypted.print("mark");
        if (ENCRYPT) {
            encrypted.print(" currentfile closefile ");
        }
        encrypted.flush();
        encEnd = byteCounter.getCount();
        if (!ENCRYPT)
            asciiEnd = encEnd;

        if (addZeros) {
            fontFile.println();
            for (int i = 0; i < 16; i++)
                fontFile.println("00000000000000000000000000000000");
            fontFile.println("cleartomark");
        }
    }

    protected void writeGlyph(String characterName, Shape glyph,
            GlyphMetrics glyphMetrics) throws IOException {

        // FIXME: find out why Acrobat Reader displays some characters displaced
        // when
        // using the correct sidebearing. A value of 0 looks good
        double sidebearing = glyphMetrics != null ? glyphMetrics.getLSB() : 0;
        // double sidebearing = 0;

        // write the binary charstring to a buffer
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        CharstringEncoder charString = (HEX_ENC_CHARSTRINGS ? new CharstringEncoder(
                new EEXECEncryption(new ASCIIHexOutputStream(bytes),
                        EEXECEncryption.CHARSTRING_R)) : new CharstringEncoder(
                new EEXECEncryption(bytes, EEXECEncryption.CHARSTRING_R)));

        charString.startChar(sidebearing,
                (glyphMetrics != null ? glyphMetrics.getAdvance()
                        : getUndefinedWidth())); // bounds.getWidth());
        charString.drawPath(glyph);
        charString.endchar();

        // write the buffer to the encrypted fontFile
        byte[] binaryString = bytes.toByteArray();
        encrypted.print("/" + characterName + " " + binaryString.length
                + " RD ");
        for (int i = 0; i < binaryString.length; i++) {
            encrypted.write(binaryString[i] & 0x00ff);
        }
        encrypted.println("ND");
        encrypted.flush();
    }

    /** Returns the length of the ascii portion of the output. */
    public int getAsciiLength() {
        return asciiEnd;
    }

    /** Returns the length of the encrypted portion of the output. */
    public int getEncryptedLength() {
        return encEnd - asciiEnd;
    }

}
